<?php
/*
 +----------------------------------------------------------------------+
 | mini-system 0.3                                                      |
 +----------------------------------------------------------------------+
 | author: 雷果国(leiguoguo@zhisland.com)                               |
 +----------------------------------------------------------------------+
 | mini-system http客户端                                               |
 +----------------------------------------------------------------------+
 */

#定义http的socket连接的创建和读取超时时间
define('HTTP_SOCKET_DEFAULT_CREATE_TIMEOUT',				1);			#SOCKET创建超时时间
define('HTTP_SOCKET_DEFAULT_READ_TIMEOUT',					1);			#SOCKET读取超时时间

#定义目前支持的HTTP方法
define('HTTP_METHOD_SUPPORTS',								'support_http_method');			#所有支持的HTTP方法
define('HTTP_METHOD_SUPPORT_GET',							'GET');							#HTTP GET方法
define('HTTP_METHOD_SUPPORT_POST',							'POST');						#HTTP POST方法
define('HTTP_METHOD_DEFAULT',								HTTP_METHOD_SUPPORT_GET);		#默认HTTP方法
$GLOBALS[HTTP_METHOD_SUPPORTS]	= array(HTTP_METHOD_SUPPORT_GET, HTTP_METHOD_SUPPORT_POST);	#所有支持的HTTP方法

#定义HTTP请求相关信息
define('IP_PATTERN',										'/^\s*(\d{1,3})(\.(?1)){3}\s*$/');		#IP地址正则
define('HTTP_STATUS_PATTERN',								'/^(HTTP\/\d\.\d)\s+(\d+)\s+(.+)$/');	#HTTP状态正则
define('HTTP_HEADER_SEP',									':');			#HTTP头名字和值的分隔符
define('HTTP_REQUEST_DEFAULT_PATH',							'/');			#HTTP默认路径
define('HTTP_REQUEST_DEFAULT_PORT',							80);			#HTTP默认端口
define('HTTP_LINE_STOP',									"\r\n");		#HTTP协议的CRLF
define('HTTP_REQUEST_DEFAULT_BUFFERSIZE',					2048);			#SOCKET读缓冲区大小

#定义HTTP请求过程中的错误编号
define('HTTP_REQUEST_ERROR_DNS',							1000);			#DNS解析出错
define('HTTP_REQUEST_ERROR_CONNECT',						1010);			#连接出错
define('HTTP_REQUEST_ERROR_WRITE',							1020);			#SOCKET写出错
define('HTTP_REQUEST_ERROR_READ_TIMEOUT',					1030);			#SOCKET读超时
define('HTTP_REQUEST_ERROR_READ',							1040);			#SOCKET读错误
#为兼容旧版http_request增加一个响应错误码
define('HTTP_REQUEST_ERROR_RESPONSE',						1001);			#HTTP响应非2xx

#定义HTTP响应key
define('HTTP_RESP_KEY_PROTOCOL',							'resp_protocol');		#响应返回的协议
define('HTTP_RESP_KEY_STATUS',								'resp_status');			#响应返回的状态描述
define('HTTP_RESP_KEY_STATUS_CODE',							'resp_status_code');	#响应返回的状态码
define('HTTP_RESP_KEY_SUCCESS',								'resp_success');		#响应是否成功
define('HTTP_RESP_KEY_HEADERS',								'resp_headers');		#响应头
define('HTTP_RESP_KEY_BODY',								'resp_body');			#响应体

#便利函数, 返回false, 将$no赋值给$errno
function http_false_return($no, &$errno) {
	$errno = $no;
	return false;
}
#将参数转换为post_data格式
function http_build_params($params) {
	if(is_array($params) || is_object($params)) return http_build_query($params);
	else if(is_string($params)) return $params;
	else return '';
}
#获取请求方法
function http_build_method($method) {
	$method	= strtoupper(strval($method));
	if(in_array($method, $GLOBALS[HTTP_METHOD_SUPPORTS])) return $method;
	return HTTP_METHOD_DEFAULT;
}
#将host转换成ip
function http_dns($host) {
	if(preg_match(IP_PATTERN, $host)) return $host;
	$host_ip	= gethostbyname($host);
	return $host_ip == $host ? false : $host_ip;
}
#构建请求头
function http_build_headers($headers) {
	if(is_string($headers) && !empty($headers)) return $headers . HTTP_LINE_STOP;
	else if(is_array($headers) && !empty($headers)) return implode(HTTP_LINE_STOP, $headers) . HTTP_LINE_STOP;
	else return '';
}
#构建请求POST数据相关头
function http_build_postdata($method, $post_data) {
	if($method == HTTP_METHOD_SUPPORT_GET) return 'Connection: Close' . str_repeat(HTTP_LINE_STOP, 2);
	$post_data_len	= strlen($post_data);
	$res	 = 	'Content-type: application/x-www-form-urlencoded' . HTTP_LINE_STOP;
	$res	.=	'Content-Length: ' . $post_data_len . HTTP_LINE_STOP;
	$res	.=	'Connection: Close' . str_repeat(HTTP_LINE_STOP, 2);
	$res	.=	$post_data . str_repeat(HTTP_LINE_STOP, 2);
	return $res;
}
#http响应处理
function http_response_handle($response) {
	$status_pos	=	strpos($response, HTTP_LINE_STOP);
	$header_pos	=	strpos($response, str_repeat(HTTP_LINE_STOP, 2));
	$status		=	substr($response, 0, $status_pos);
	$header		=	substr($response, $status_pos + 2, $header_pos - $status_pos - 2);
	$body		=	substr($response, $header_pos + 4);
	$response	=	array();
	preg_match(HTTP_STATUS_PATTERN, $status, $matches_status);
	$response_success = $matches_status[2][0] == '2';
	$headers	=	http_response_header($header);
	$response[HTTP_RESP_KEY_PROTOCOL]		= $matches_status[1];
	$response[HTTP_RESP_KEY_STATUS_CODE]	= $matches_status[2];
	$response[HTTP_RESP_KEY_STATUS]			= $matches_status[3];
	$response[HTTP_RESP_KEY_SUCCESS]		= $response_success;
	$response[HTTP_RESP_KEY_HEADERS]		= $headers;
	$response[HTTP_RESP_KEY_BODY]			= $body;
	return $response;
}
#将字符串的http响应头转换为数组
function http_response_header($header) {
	$headers	=	array();
	$header		=	explode(HTTP_LINE_STOP, $header);
	foreach($header as $item) {
		$item = explode(HTTP_HEADER_SEP, $item, 2);
		if(empty($item)) continue;
		$header_name	=	$item[0];
		$header_value	=	$item[1];
		$headers[$header_name] = $header_value;
	}
	return $headers;
}
#http请求基础接口
function http_socket_ip_request($host, $path, $method, &$errno, $headers = array(), $params = array(), $port = HTTP_REQUEST_DEFAULT_PORT, 
		$ctimeout = HTTP_SOCKET_DEFAULT_CREATE_TIMEOUT, $rtimeout = HTTP_SOCKET_DEFAULT_READ_TIMEOUT, $ip = NULL) {
	$method		= http_build_method($method);
	$host_ip	= isset($ip) ? $ip : http_dns($host);
	if($host_ip === false) return http_false_return(HTTP_REQUEST_ERROR_DNS, $errno);
	$post_data	= http_build_params($params);
	if(empty($path) || !is_string($path)) $path = HTTP_REQUEST_DEFAULT_PATH;
	if($method	== HTTP_METHOD_SUPPORT_GET) $path .= '?' . $post_data;
	$fsock		= fsockopen($host_ip, $port, $errno, $errstr, $ctimeout);
	if(false == $fsock) return http_false_return(HTTP_REQUEST_ERROR_CONNECT, $errno);
	$quest_str	 =	$method . ' ' . $path . ' HTTP/1.0' . HTTP_LINE_STOP;
	$quest_str	.=	'Accept: */*' . HTTP_LINE_STOP;
	$quest_str	.=	'Host: ' . $host . HTTP_LINE_STOP;
	$quest_str	.=	http_build_headers($headers);
	$quest_str	.=	http_build_postdata($method, $post_data);
	if(!fwrite($fsock, $quest_str, strlen($quest_str))) {
		fclose($fsock);
		return http_false_return(HTTP_REQUEST_ERROR_WRITE, $errno);
	}
	stream_set_timeout($fsock, $rtimeout);
	$response_str	=	'';
	while($buff = fread($fsock, HTTP_REQUEST_DEFAULT_BUFFERSIZE)) $response_str .= $buff;
	fclose($fsock);
	if(empty($response_str)) {
		$stream_meta	= stream_get_meta_data($fsock);
		if($stream_meta['timed_out']) return http_false_return(HTTP_REQUEST_ERROR_READ_TIMEOUT, $errno);
		return http_false_return(HTTP_REQUEST_ERROR_READ, $errno);
	}
	$response	=	http_response_handle($response_str);
	return $response;
}
#http请求基础接口
function http_socket_request($host, $path, $method, &$errno, $headers = array(), $params = array(), $port = HTTP_REQUEST_DEFAULT_PORT, 
		$ctimeout = HTTP_SOCKET_DEFAULT_CREATE_TIMEOUT, $rtimeout = HTTP_SOCKET_DEFAULT_READ_TIMEOUT) {
	return http_socket_ip_request($host, $path, $method, $errno, $headers, $params, $port, $ctimeout, $rtimeout);
}
#使用新的socket封装旧版本接口
function http_request($url, $post_string, $header, &$errno, $ctimeout = 1, $rtimeout = 1 , $return_type = HTTP_RESP_KEY_BODY){
	$url_info	= parse_url($url);
	$host		= $url_info['host'];
	$path		= isset($url_info['path']) ? $url_info['path'] : '/';
	$port		= isset($url_info['port']) ? $url_info['port'] : 80;
	$query		= isset($url_info['query']) ? $url_info['query'] : false;
	$fragment	= isset($url_info['fragment']) ?$url_info['fragment'] : false;
	if($query) $path .= '?' . $query;
	if($fragment) $path .= '#' . $fragment;
	$response = http_socket_request($host, $path, empty($post_string) ? HTTP_METHOD_SUPPORT_GET : HTTP_METHOD_SUPPORT_POST, $errno, $header, $post_string, $port, $ctimeout, $rtimeout);
	if ( $response ) {
		if ( $response[HTTP_RESP_KEY_SUCCESS] ) {
			if($return_type && isset($response[$return_type])) return $response[$return_type];
			return $response;
		} else return http_false_return(HTTP_REQUEST_ERROR_RESPONSE, $errno);
	} else {
		return false;
	}
}
